using System;
using System.IO;
using System.Collections.Generic;
using System.Net;
using System.Web;
using System.Text;
using System.Xml;
using System.Runtime.Serialization.Formatters.Binary;

namespace Eve.API
{
    /// <summary>
    /// Specifies the character attributes used by Eve
    /// </summary>
    [Serializable]
    public enum Attribute : int
    {
        /// <summary>
        /// Specifies the Percepetion attribute.
        /// </summary>
        Perception = 1,
        /// <summary>
        /// Specifies the Willpower attribute
        /// </summary>
        Willpower = 2,
        /// <summary>
        /// Specifies the Charisma attribute
        /// </summary>
        Charisma = 3,
        /// <summary>
        /// Specifies the Memory attribute
        /// </summary>
        Memory = 4,
        /// <summary>
        /// Specifies the Intelligence attribute
        /// </summary>
        Intelligence = 5,
    }
    /// <summary>
    /// Specifies the account to be queried
    /// </summary>
    [Serializable]
    public enum WalletAccountKey : int
    {
        /// <summary>
        /// Use the character's main wallet
        /// </summary>
        Character = -1000,
        /// <summary>
        /// Use the character's corporation's master wallet
        /// </summary>
        CorporateMaster = 1000,
        /// <summary>
        /// Use the character's corporation's 2nd division wallet
        /// </summary>
        Corporate2 = 1001,
        /// <summary>
        /// Use the character's corporation's 3rd division wallet
        /// </summary>
        Corporate3 = 1002,
        /// <summary>
        /// Use the character's corporation's 4th division wallet
        /// </summary>
        Corporate4 = 1003,
        /// <summary>
        /// Use the character's corporation's 5th division wallet
        /// </summary>
        Corporate5 = 1004,
        /// <summary>
        /// Use the character's corporation's 6th division wallet
        /// </summary>
        Corporate6 = 1005,
        /// <summary>
        /// Use the character's corporation's 7th division wallet
        /// </summary>
        Corporate7 = 1006,
    }
    /// <summary>
    /// Represents a single wallet
    /// </summary>
    [Serializable]
    public struct WalletAccount
    {
        /// <summary>
        /// The value used by Eve to identify this specific wallet
        /// </summary>
        public int Id;
        /// <summary>
        /// The current wallet ISK balance
        /// </summary>
        public double Balance;
    }
    /// <summary>
    /// Represents a Name/Identifier pair.
    /// </summary>
    [Serializable]
    public struct NameId
    {
        /// <summary>
        /// The user-readable name of the item being represented
        /// </summary>
        /// <value>
        /// A string representing the user-readable name of the object
        /// </value>
        public string Name;
        /// <summary>
        /// The Eve internal identifier for the item being represented
        /// </summary>
        /// <value>
        /// An integer representing the Eve internal identifier of the item being represented
        /// </value>
        public int Id;
    }
    /// <summary>
    /// The basis for all Eve API functions.
    /// </summary>
    /// <remarks>
    /// The <b>EveAPI</b> class provides a set of methods that allow access to all the API functions provided by CCP.  
    /// <para>
    /// The Eve API requires the use of an User Id/API Key pair, which is unique for every user (not each character).
    /// There are two types of API key that can be generated: a limited key, and a full access key.  The limited key provides access to
    /// only the character and kill training information, while the full access key allows all data to be retrieved.  The type of key that should be provided 
    /// depends on which methods the application will be accessing.  If a limited key is used to access non- character or skill methods, a security error
    /// will be returned.  Applications which require a full access key should inform the user of the risks associated with exposing balance, transaction, and logon data.
    /// <note type="note">
    /// API Keys can be generated at http://myeve.eve-online.com/api/default.asp.
    /// </note>
    /// </para>
    /// <para>
    /// The <b>EveAPI</b> class exposes three static methods (<see cref="Eve.API.EveAPI.GetSkillTree">GetSkillTree</see>, <see cref="Eve.API.EveAPI.GetRefTypes">GetRefTypes</see>, and <see cref="Eve.API.EveAPI.GetSovereignty">GetSovereignty</see>), 
    /// which do not require API Key authentication. The results from these three methods are shared across all instances of the <b>EveAPI</b> class.
    /// </para>
    /// <para>
    /// All results returned from the Eve API are cached internally in accordance with the <i>cacheUntil</i> parameter returned by CCP.  When a cached result is used, <b>EveAPI</b> methods will return a value of <c>ErrorCode.ResultCached</c> instead of <c>ErrorCode.Success</c>.
    /// To persist this cache across sessions, the <see cref="Eve.API.EveAPI.PersistStaticCache">PersistStaticCache</see> and <see cref="Eve.API.EveAPI.PersistUserCache">PersistUserCache</see> methods should be used for the non-authenticated and user-specific results, respectively.
    /// The default location of the cache files is the current working directory, and the default file names are <c>Eve.API.Cache.Static.dat</c> for the non-authenticated results, and <c>Eve.API.Cache.</c><i>UserId</i><c>.dat</c> for the user-specific results.
    /// These file locations can be changed by setting the <see cref="Eve.API.EveAPI.StaticCacheFile">StaticCacheFile</see> or <see cref="Eve.API.EveAPI.UserCacheFile">UserCacheFile</see> properties.
    /// </para>
    /// </remarks>
    /// <example>
    /// <para>The following code example creates an instance of the <b>EveAPI</b> class and retrieves character sheet and skill training information.</para>
    /// <code lang="C#">
    /// using System;
    /// using Eve.API;
    /// 
    /// public class Example
    /// {
    ///     public static void Main()
    ///     {
    ///         EveAPI.Error err;
    /// 
    ///         // First get the skill tree
    ///         List&lt;SkillGroup&gt; skillTree;
    ///         err = EveAPI.GetSkillTree(out skillTree);
    ///         if (err.Code != EveAPI.ErrorCode.Success &amp;&amp; err.Code != EveAPI.ErrorCode.ResultCached)
    ///         {
    ///             Console.WriteLine("GetSkillTree: Error " + err.Code + ": " + err.Message);
    ///         }
    /// 
    ///         // Create an instance of EveAPI
    ///         EveAPI api = new EveAPI(12345, "LmSjbo1rrXcUqYwxJ13HihJ67cgVA7IyvyZP1VvCWs9htptqTVddH9TmUNS0ExsV");
    ///
    ///         // Get a list of all characters on this account 
    ///         List&lt;CharacterInfo&gt; characters;
    ///         err = api.GetCharacters(out characters);
    ///         if (err.Code == EveAPI.ErrorCode.Success || err.Code == EveAPI.ErrorCode.ResultCached)
    ///         {
    ///             foreach (CharacterInfo charInfo in characters)
    ///             {
    ///                 // Set the character Id for which we want to get information
    ///                 api.CharacterId = charInfo.Character.Id;
    ///                 Console.WriteLine("GetCharacters: Using " + charInfo.Character.Name + "(Id: " + charInfo.Character.Id + ")");
    ///             
    ///                 CharacterSheet charSheet;
    ///                 SkillTraining skillTraining;
    /// 
    ///                 // Get the character's basic information
    ///                 err = api.GetCharacterSheet(out charSheet);
    ///                 if (err.Code != EveAPI.ErrorCode.Success &amp;&amp; err.Code != EveAPI.ErrorCode.ResultCached)
    ///                 {
    ///                     Console.WriteLine("GetCharacterSheet: Error " + err.Code + ": " + err.Message);
    ///                 }
    /// 
    ///                 // Get the charater's current skill in training
    ///                 err = api.GetSkillInTraining(out skillTraining);
    ///                 if (err.Code != EveAPI.ErrorCode.Success &amp;&amp; err.Code != EveAPI.ErrorCode.ResultCached)
    ///                 {
    ///                     Console.WriteLine("GetSkillTraining: Error " + err.Code + ": " + err.Message);
    ///                 }
    /// 
    ///                 // Display the character information here
    ///             } 
    ///         }
    /// 
    ///         // Persist the cached information
    ///         api.PersistUserCache();
    ///         api.PersistStaticCache();
    ///     }
    /// }
    /// </code>
    /// <code lang="vb">
    /// Imports System
    /// Imports Eve.API
    /// 
    /// Public Class Example
    /// 
    ///     Public Shared Sub Main()
    ///     
    ///         Dim err As EveAPI.Error
    /// 
    ///         ' First get the skill tree
    ///         Dim skillTree As List(Of SkillGroup)
    ///         err = EveAPI.GetSkillTree(skillTree)
    ///         If Not err.Code = EveAPI.ErrorCode.Success And Not err.Code = EveAPI.ErrorCode.ResultCached Then
    ///             Console.WriteLine("GetSkillTree: Error " + err.Code + ": " + err.Message)
    ///         End If
    /// 
    ///         ' Create an instance of EveAPI
    ///         Dim api As New EveAPI(12345, "LmSjbo1rrXcUqYwxJ13HihJ67cgVA7IyvyZP1VvCWs9htptqTVddH9TmUNS0ExsV")
    ///
    ///         ' Get a list of all characters on this account 
    ///         Dim characters As List(Of CharacterInfo)
    ///         err = api.GetCharacters(characters)
    ///         If err.Code = EveAPI.ErrorCode.Success Or err.Code = EveAPI.ErrorCode.ResultCached Then
    ///             For Each charInfo As CharacterInfo in characters
    /// 
    ///                 ' Set the character Id for which we want to get information
    ///                 api.CharacterId = charInfo.Character.Id
    ///                 Console.WriteLine("GetCharacters: Using " + charInfo.Character.Name + "(Id: " + charInfo.Character.Id + ")")
    ///             
    ///                 Dim charSheet As CharacterSheet
    ///                 Dim skillTraining As SkillTraining
    /// 
    ///                 ' Get the character's basic information
    ///                 err = api.GetCharacterSheet(charSheet)
    ///                 If Not err.Code = EveAPI.ErrorCode.Success And Not err.Code = EveAPI.ErrorCode.ResultCached Then
    ///                     Console.WriteLine("GetCharacterSheet: Error " + err.Code + ": " + err.Message)
    ///                 End If
    /// 
    ///                 ' Get the charater's current skill in training
    ///                 err = api.GetSkillInTraining(skillTraining)
    ///                 If Not err.Code = EveAPI.ErrorCode.Success And Not err.Code = EveAPI.ErrorCode.ResultCached Then
    ///                     Console.WriteLine("GetSkillTraining: Error " + err.Code + ": " + err.Message)
    ///                 End If
    /// 
    ///                 ' Display the character information here
    /// 
    ///             Next 
    ///         End If
    /// 
    ///         ' Persist the cached information
    ///         api.PersistUserCache()
    ///         api.PersistStaticCache()
    ///     }
    /// }
    /// </code>
    /// </example>
    public class EveAPI
    {
        [Serializable]
        private class Cache<T>
        {
            private TimeSpan diff = new TimeSpan();
            public DateTime CacheUntil = DateTime.MinValue;
            public T Value = default(T);

            public bool UseCached
            {
                get { return (DateTime.Now + diff) < CacheUntil; }
            }

            public Cache() { }
            public Cache(TimeSpan timeDiff)
            {
                diff = timeDiff;
            }
        }

        /// <summary>
        /// Specifies the <b>EveAPI</b> method result.
        /// </summary>
        public enum ErrorCode
        {
            /// <summary>
            /// The <c>Eve.API</c> version being used does not support the Eve API version returned by CCP.
            /// </summary>
            UnsupportedVersion = -1,
            /// <summary>
            /// The operation completed successfully.
            /// </summary>
            Success = 0,
            /// <summary>
            /// <c>Eve.API</c> returned a cached result because the caching period specified by the API server has not lapsed.
            /// </summary>
            ResultCached = 1,
        }
        /// <summary>
        /// Provides information about an <b>EveAPI</b> method result.
        /// </summary>
        public class Error
        {
            /// <summary>
            /// The <c>Eve.API</c> specific error code or a value returned by the API server.
            /// </summary>
            /// <remarks>
            /// <para>A value of <b>ErrorCode.Success</b> indicates that the operation completed successfully and returned new data.</para>
            /// <para>A value of <b>ErrorCode.ResultCached</b> indicates that the operation completed successfully and returned cached data.</para>
            /// <para>A value of <b>ErrorCode.UnsupportedVersion</b> indicates that EveAPI doesn't support the version of data return by CCP.</para>
            /// <para>Any other value indicates an error returned by CCP, with the <see cref="Eve.API.EveAPI.Error.Message">Message</see> property providing a description of the error.</para>
            /// </remarks>
            public ErrorCode Code;
            /// <summary>
            /// A message specific to the cause of the error.
            /// </summary>
            public string Message;

            internal Error(ErrorCode code, string msg)
            {
                this.Code = code;
                this.Message = msg;
            }
        }

        [Serializable]
        private class CharacterCache
        {
            private Cache<CharacterSheet> characterSheetCache = new Cache<CharacterSheet>();
            private Cache<SkillTraining> skillTrainingCache = new Cache<SkillTraining>();
            private Dictionary<WalletAccountKey, Dictionary<int, Cache<List<WalletJournalEntry>>>> walletJournalCache = new Dictionary<WalletAccountKey, Dictionary<int, Cache<List<WalletJournalEntry>>>>();
            private Dictionary<WalletAccountKey, Dictionary<int, Cache<List<WalletTransaction>>>> walletTransactionCache = new Dictionary<WalletAccountKey, Dictionary<int, Cache<List<WalletTransaction>>>>();
            private Cache<Dictionary<WalletAccountKey, WalletAccount>> charBalanceCache = new Cache<Dictionary<WalletAccountKey, WalletAccount>>();
            private Cache<Dictionary<WalletAccountKey, WalletAccount>> corpBalanceCache = new Cache<Dictionary<WalletAccountKey, WalletAccount>>();
            private Cache<List<MemberTracking>> memberTrackingCache = new Cache<List<MemberTracking>>();

            public Cache<CharacterSheet> CharacterSheet
            {
                get { return characterSheetCache; }
                set { characterSheetCache = value; }
            }
            public Cache<SkillTraining> SkillTraining
            {
                get { return skillTrainingCache; }
                set { skillTrainingCache = value; }
            }
            public Dictionary<WalletAccountKey, Dictionary<int, Cache<List<WalletJournalEntry>>>> WalletJournal
            {
                get { return walletJournalCache; }
                set { walletJournalCache = value; }
            }
            public Dictionary<WalletAccountKey, Dictionary<int, Cache<List<WalletTransaction>>>> WalletTransaction
            {
                get { return walletTransactionCache; }
                set { walletTransactionCache = value; }
            }
            public Cache<Dictionary<WalletAccountKey, WalletAccount>> CharacterBalance
            {
                get { return charBalanceCache; }
                set { charBalanceCache = value; }
            }
            public Cache<Dictionary<WalletAccountKey, WalletAccount>> CorporateBalance
            {
                get { return corpBalanceCache; }
                set { corpBalanceCache = value; }
            }
            public Cache<List<MemberTracking>> MemberTracking
            {
                get { return memberTrackingCache; }
                set { memberTrackingCache = value; }
            }

            public CharacterCache()
            {
            }
        }
        [Serializable]
        private class UserCache
        {
            private Dictionary<int, CharacterCache> characterCache = new Dictionary<int,CharacterCache>();
            private Cache<List<CharacterInfo>> characterInfoCache = new Cache<List<CharacterInfo>>();

            public CharacterCache this[int charId]
            {
                get
                {
                    if (!characterCache.ContainsKey(charId))
                        characterCache[charId] = new CharacterCache();
                    return characterCache[charId];
                }
            }
            public Cache<List<CharacterInfo>> CharacterInfo
            {
                get { return characterInfoCache; }
                set { characterInfoCache = value; }
            }

            public UserCache()
            {
            }
        }

        private const int UserCacheVersion = 2;
        private const int SupportedVersion = 1;
        private const string EveAPIURL = "api.eve-online.com";

        private static Error success = new Error(ErrorCode.Success, "");
        private static Error cached = new Error(ErrorCode.ResultCached, "");
        private static Error unsupported = new Error(ErrorCode.UnsupportedVersion, "");

        private static DateTime currentEveTime = DateTime.MinValue;
        private static TimeSpan timeDiff = new TimeSpan();

        private static bool staticCacheRetrieved = false;
        private static string staticCacheFile = "";
        private static Cache<List<SkillGroup>> skillTreeCache = new Cache<List<SkillGroup>>();
        private static Cache<Dictionary<int, string>> refTypeCache = new Cache<Dictionary<int,string>>();
        private static Cache<List<SolarSystem>> solarSystemCache = new Cache<List<SolarSystem>>();

        private static bool userCacheRetrieved = false;
        private string userCacheFile = "";
        private UserCache userCache = new UserCache();

        private int userId;
        private string apiKey;
        private int characterId = 0;

        /// <summary>
        /// Gets or sets the location to which <b>EveAPI</b> will write the statically cached data.
        /// </summary>
        /// <value>
        /// The path and filename of the location to write statically cached data.
        /// </value>
        /// <remarks>
        /// The default static cache file is <c>Eve.API.Cache.Static.dat</c>, and resides in the working directory.  
        /// To cause <b>EveAPI</b> to persist the cache in a different location, set this property to the file name
        /// where it should be saved.  Upon setting this property, <b>EveAPI</b> will attempt to move any existing cache
        /// information to the new location, or if no cache is present, will attempt to create a cache file at that location.
        /// </remarks>
        /// <exception cref="IOException">The cache file could not be moved or created.</exception>
        public static string StaticCacheFile
        {
            get 
            {
                if (staticCacheFile == "") staticCacheFile = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + Path.DirectorySeparatorChar + "Eve.API.Cache.Static.dat";
                return staticCacheFile; 
            }
            set
            {
                if (value != staticCacheFile)
                {
                    if (File.Exists(staticCacheFile)) File.Move(staticCacheFile, value);
                    else if (!File.Exists(value))
                    {
                        FileStream tfs = File.Create(value);
                        tfs.Close();
                    }
                    else
                    {
                        FileStream tfs = File.Open(value, FileMode.Open, FileAccess.Write, FileShare.None);
                        tfs.Close();
                    }
                    staticCacheFile = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the current character identifier
        /// </summary>
        /// <value>The character identifier that will be used when accessing character-specific data.</value>
        /// <remarks>
        /// <para>The Eve API requires a character identifier to access character-specific data. This value is unique to the specific character and is generated by Eve.</para>
        /// <para>When retrieving corporation related data, the API will query the corporation that the character specified is a member of, and only return information that 
        /// the character has permission to view within the game.</para>
        /// </remarks>
        /// <example>
        /// The following example shows how to retrieve a list of characters for an account, and use the result to set the <b>CharacterId</b> property.
        /// <code lang="C#">
        ///         // Create an instance of EveAPI
        ///         EveAPI.Error err;
        ///         EveAPI api = new EveAPI(12345, "LmSjbo1rrXcUqYwxJ13HihJ67cgVA7IyvyZP1VvCWs9htptqTVddH9TmUNS0ExsV");
        ///
        ///         // Get a list of all characters on this account 
        ///         List&lt;CharacterInfo&gt; characters;
        ///         err = api.GetCharacters(out characters);
        ///         if (err.Code == EveAPI.ErrorCode.Success || err.Code == EveAPI.ErrorCode.ResultCached)
        ///         {
        ///                 // Set the character Id for which we want to get information
        ///                 api.CharacterId = characters[0].Character.Id;
        ///         }
        /// </code>
        /// <code lang="vb">
        ///         ' Create an instance of EveAPI
        ///         Dim err As EveAPI.Error
        ///         Dim api As New EveAPI(12345, "LmSjbo1rrXcUqYwxJ13HihJ67cgVA7IyvyZP1VvCWs9htptqTVddH9TmUNS0ExsV")
        ///
        ///         ' Get a list of all characters on this account 
        ///         Dim characters As List(Of CharacterInfo)
        ///         err = api.GetCharacters(characters)
        ///         If err.Code = EveAPI.ErrorCode.Success Or err.Code = EveAPI.ErrorCode.ResultCached Then
        /// 
        ///                 ' Set the character Id for which we want to get information
        ///                 api.CharacterId = characters[0].Character.Id
        ///         End If
        /// </code>
        /// </example>
        public int CharacterId
        {
            get { return characterId; }
            set { characterId = value; }
        }
        /// <summary>
        /// Gets or sets the location to which <b>EveAPI</b> will write cached data that is specific to a user account.
        /// </summary>
        /// <value>
        /// The path and filename of the location to write cached user data.
        /// </value>
        /// <remarks>
        /// <note type="note">Only one user cache file is required for each User Id/API Key pair.</note>
        /// The default user cache file is <c>Eve.API.Cache.</c><i>&lt;User Id&gt;</i><c>.dat</c>, and resides in the current users's local application data directory.  
        /// To cause <b>EveAPI</b> to persist the cache in a different location, set this property to the file name
        /// where it should be saved.  Upon setting this property, <b>EveAPI</b> will attempt to move any existing cache
        /// information to the new location, or if no cache is present, will attempt to create a cache file at that location.
        /// </remarks>
        /// <exception cref="IOException">The cache file could not be moved or created.</exception>
        public string UserCacheFile
        {
            get
            {
                if (userCacheFile == "") userCacheFile = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + Path.DirectorySeparatorChar + "Eve.API.Cache." + userId.ToString() + ".dat";
                return userCacheFile;
            }
            set
            {
                if (value != userCacheFile)
                {
                    if (File.Exists(userCacheFile)) File.Move(userCacheFile, value);
                    else if (!File.Exists(value))
                    {
                        FileStream tfs = File.Create(value);
                        tfs.Close();
                    }
                    else
                    {
                        FileStream tfs = File.Open(value, FileMode.Open, FileAccess.Write, FileShare.None);
                        tfs.Close();
                    }
                    userCacheFile = value;
                }
            }
        }

        /// <overloads>This constructor has two overloads.</overloads>
        /// <summary>
        /// Creates a new instance of the <b>EveAPI</b> class.
        /// </summary>
        /// <param name="userId">The user identifier of the account to retrieve information from.</param>
        /// <param name="apiKey">The API key of the account to retrieve information from.</param>
        /// <remarks>
        /// <para>
        /// This constructor should be used when a specific character identifier is not known.  
        /// The character identifier must be set using the <see cref="Eve.API.EveAPI.CharacterId">CharacterId</see> property before calling
        /// any methods other than <see cref="Eve.API.EveAPI.GetCharacters">GetCharacters</see>.
        /// </para>
        /// <para>
        /// Initializing <b>EveAPI</b> with a limited-access API key will only allow access to character sheet and skill methods.  
        /// In order to retrieve wallet or corporation member tracking information, a full-access API key must be specified.
        /// Applications which require a full access key should inform the user of the risks associated with exposing balance, transaction, and logon data.
        /// <note type="note">
        /// API Keys can be generated at http://myeve.eve-online.com/api/default.asp.
        /// </note>        
        /// </para>
        /// </remarks>
        public EveAPI(int userId, string apiKey)
        {
            this.userId = userId;
            this.apiKey = apiKey;
        }
        /// <summary>
        /// Creates a new instance of the <b>EveAPI</b> class.
        /// </summary>
        /// <param name="userId">The user identifier of the account to retrieve information from.</param>
        /// <param name="apiKey">The API key of the account to retrieve information from.</param>
        /// <param name="characterId">The character identifier associated with the account for which information should be retrieved</param>
        /// <remarks>
        /// <para>
        /// Initializing <b>EveAPI</b> with a limited-access API key will only allow access to character sheet and skill methods.  
        /// In order to retrieve wallet or corporation member tracking information, a full-access API key must be specified.
        /// Applications which require a full access key should inform the user of the risks associated with exposing balance, transaction, and logon data.
        /// <note type="note">
        /// API Keys can be generated at http://myeve.eve-online.com/api/default.asp.
        /// </note>        
        /// </para>
        /// </remarks>
        public EveAPI(int userId, string apiKey, int characterId)
        {
            this.userId = userId;
            this.apiKey = apiKey;
            this.characterId = characterId;
        }

        /// <summary>
        /// Gets the entire published skill tree.
        /// </summary>
        /// <param name="groups">[out] A list of skill groups containing all published skills.</param>
        /// <returns>An <see cref="Eve.API.EveAPI.Error">Error</see> class representing the result of the operation.</returns>
        public static Error GetSkillTree(out List<SkillGroup> groups)
        {
            RetrieveStaticCache();
            if (skillTreeCache != null && skillTreeCache.UseCached)
            {
                groups = new List<SkillGroup>(skillTreeCache.Value);
                return cached;
            }
            XmlElement result;
            Error ret = success;
            DateTime cacheUntil;
            groups = new List<SkillGroup>();
            
            ret = GetXmlResult(SendXmlRequest("/eve/SkillTree.xml.aspx"), out result, out cacheUntil);
            if (ret.Code != ErrorCode.Success) return ret;

            foreach (XmlElement rowset in result.ChildNodes)
            {
                if (rowset.Attributes["name"].Value == "skillGroups")
                {
                    foreach (XmlElement skillGroup in rowset.ChildNodes)
                        groups.Add(new SkillGroup(skillGroup));
                }
            }
            skillTreeCache = new Cache<List<SkillGroup>>(timeDiff);
            skillTreeCache.Value = new List<SkillGroup>(groups);
            skillTreeCache.CacheUntil = cacheUntil;
            PersistStaticCache();
            return ret;
        }
        /// <summary>
        /// Gets the list of wallet journal reference type identifiers and their descriptions.
        /// </summary>
        /// <param name="refTypes">[out] A <b>Dictionary</b> containing key/value pairs describing all the wallet journal reference types in the game.</param>
        /// <returns>An <see cref="Eve.API.EveAPI.Error">Error</see> class representing the result of the operation.</returns>
        public static Error GetRefTypes(out Dictionary<int, string> refTypes)
        {
            RetrieveStaticCache();
            if (refTypeCache != null && refTypeCache.UseCached)
            {
                refTypes = refTypeCache.Value;
                return cached;
            }
            XmlElement result;
            Error ret = success;
            DateTime cacheUntil;
            refTypes = new Dictionary<int, string>();
            ret = GetXmlResult(SendXmlRequest("/eve/RefTypes.xml.aspx"), out result, out cacheUntil);
            if (ret.Code != ErrorCode.Success) return ret;

            foreach (XmlElement rowset in result.ChildNodes)
            {
                if (rowset.Attributes["name"].Value == "refTypes")
                {
                    foreach (XmlElement row in rowset.ChildNodes)
                        refTypes.Add(int.Parse(row.Attributes["refTypeID"].Value), row.Attributes["refTypeName"].Value);
                }
            }
            refTypeCache = new Cache<Dictionary<int, string>>(timeDiff);
            refTypeCache.Value = refTypes;
            refTypeCache.CacheUntil = cacheUntil;
            PersistStaticCache();
            return ret;
        }
        /// <summary>
        /// Gets a list of solar system soveriegnty information.
        /// </summary>
        /// <param name="solarSystems">[out] A list of solar system sovereignty information.</param>
        /// <returns>An <see cref="Eve.API.EveAPI.Error">Error</see> class representing the result of the operation.</returns>
        public static Error GetSovereignty(out List<SolarSystem> solarSystems)
        {
            RetrieveStaticCache();
            if (solarSystemCache != null && solarSystemCache.UseCached)
            {
                solarSystems = new List<SolarSystem>(solarSystemCache.Value);
                return cached;
            }
            XmlElement result;
            Error ret = success;
            DateTime cacheUntil;
            solarSystems = new List<SolarSystem>();
            ret = GetXmlResult(SendXmlRequest("/map/Sovereignty.xml.aspx"), out result, out cacheUntil);
            if (ret.Code != ErrorCode.Success) return ret;

            foreach (XmlElement rowset in result.ChildNodes)
            {
                if (rowset.Attributes["name"].Value == "solarSystems")
                {
                    foreach (XmlElement row in rowset.ChildNodes)
                        solarSystems.Add(new SolarSystem(row));
                }
            }
            solarSystemCache = new Cache<List<SolarSystem>>(timeDiff);
            solarSystemCache.Value = new List<SolarSystem>(solarSystems);
            solarSystemCache.CacheUntil = cacheUntil;
            PersistStaticCache();
            return ret;
        }
        /// <summary>
        /// Causes <b>EveAPI</b> to write any statically cached data to disk, so that it will persist across sessions.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method should be called when the <see cref="Eve.API.EveAPI.GetSkillTree">EveAPI.GetSkillTree</see>, <see cref="Eve.API.EveAPI.GetRefTypes">EveAPI.GetRefTypes</see>, and <see cref="Eve.API.EveAPI.GetSovereignty">EveAPI.GetSovereignty</see>
        /// methods are used.  This will allow <b>EveAPI</b> to persist cached data across sessions and reduce load on the CCP servers.
        /// </para>
        /// <para>To change the location to which the data is cached set the <see cref="Eve.API.EveAPI.StaticCacheFile">StaticCacheFile</see> property.</para>
        /// </remarks>
        /// <seealso cref="Eve.API.EveAPI.StaticCacheFile"/>
        private static void PersistStaticCache()
        {
            FileStream fs = new FileStream(StaticCacheFile, FileMode.Create, FileAccess.Write, FileShare.None);
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(fs, skillTreeCache);
            bf.Serialize(fs, refTypeCache);
            bf.Serialize(fs, solarSystemCache);
            fs.Close();
        }
        private static void RetrieveStaticCache()
        {
            if (staticCacheRetrieved) return;
            staticCacheRetrieved = true;
            FileStream fs;
            try { fs = new FileStream(StaticCacheFile, FileMode.Open, FileAccess.Read, FileShare.Read); }
            catch { return; }
            if (fs == null) return;
            BinaryFormatter bf = new BinaryFormatter();
            skillTreeCache = (Cache<List<SkillGroup>>)bf.Deserialize(fs);
            refTypeCache = (Cache<Dictionary<int, string>>)bf.Deserialize(fs);
            solarSystemCache = (Cache<List<SolarSystem>>)bf.Deserialize(fs);
            fs.Close();
        }

        /// <summary>
        /// Gets a list of characters associated with the account.
        /// </summary>
        /// <param name="chars">[out] A list of characters associated with the account.</param>
        /// <returns>An <see cref="Eve.API.EveAPI.Error">Error</see> class representing the result of the operation.</returns>
        public Error GetCharacters(out List<CharacterInfo> chars)
        {
            RetrieveUserCache();
            if (userCache.CharacterInfo != null && userCache.CharacterInfo.UseCached)
            {
                chars = new List<CharacterInfo>(userCache.CharacterInfo.Value);
                return cached;
            }
            XmlElement result;
            Error ret = success;
            chars = new List<CharacterInfo>();
            Dictionary<string, string> parameters = new Dictionary<string, string>();
            DateTime cacheUntil;
            parameters["userID"] = userId.ToString();
            parameters["apiKey"] = apiKey;
            ret = GetXmlResult(SendXmlRequest("/account/Characters.xml.aspx", parameters), out result, out cacheUntil);
            if (ret.Code != ErrorCode.Success) return ret;
            foreach (XmlElement rowset in result.ChildNodes)
            {
                if (rowset.Attributes["name"].Value == "characters")
                {
                    foreach (XmlElement row in rowset.ChildNodes)
                        chars.Add(new CharacterInfo(row));
                }
            }
            userCache.CharacterInfo = new Cache<List<CharacterInfo>>(timeDiff);
            userCache.CharacterInfo.Value = new List<CharacterInfo>(chars);
            userCache.CharacterInfo.CacheUntil = cacheUntil;
            PersistUserCache();
            return ret;
        }
        /// <summary>
        /// Gets the character sheet information for the current character identifier.
        /// </summary>
        /// <param name="sheet">[out] A <see cref="Eve.API.CharacterSheet">CharacterSheet</see> class containing information about the character.</param>
        /// <returns>An <see cref="Eve.API.EveAPI.Error">Error</see> class representing the result of the operation.</returns>
        public Error GetCharacterSheet(out CharacterSheet sheet)
        {
            RetrieveUserCache();
            if (userCache[CharacterId].CharacterSheet != null && userCache[CharacterId].CharacterSheet.UseCached)
            {
                sheet = userCache[CharacterId].CharacterSheet.Value;
                return cached;
            }
            XmlElement result;
            Error ret = success;
            sheet = null;
            Dictionary<string, string> parameters = new Dictionary<string, string>();
            DateTime cacheUntil;
            parameters["userID"] = userId.ToString();
            parameters["apiKey"] = apiKey;
            parameters["characterID"] = characterId.ToString();
            ret = GetXmlResult(SendXmlRequest("/char/CharacterSheet.xml.aspx", parameters), out result, out cacheUntil);
            if (ret.Code != ErrorCode.Success) return ret;
            sheet = new CharacterSheet(result);
            userCache[CharacterId].CharacterSheet = new Cache<CharacterSheet>(timeDiff);
            userCache[CharacterId].CharacterSheet.Value = sheet;
            userCache[CharacterId].CharacterSheet.CacheUntil = cacheUntil;
            PersistUserCache();
            return ret;
        }
        /// <summary>
        /// Gets information about the current skill a character is training.
        /// </summary>
        /// <param name="training">[out] A <see cref="Eve.API.SkillTraining">SkillTraining</see> class containing information about the currently training skill.</param>
        /// <returns>An <see cref="Eve.API.EveAPI.Error">Error</see> class representing the result of the operation.</returns>
        public Error GetSkillInTraining(out SkillTraining training)
        {
            RetrieveUserCache();
            if (userCache[CharacterId].SkillTraining != null && userCache[CharacterId].SkillTraining.UseCached)
            {
                training = userCache[CharacterId].SkillTraining.Value;
                return cached;
            }
            XmlElement result;
            Error ret = success;
            training = null;
            Dictionary<string, string> parameters = new Dictionary<string, string>();
            DateTime cacheUntil;
            parameters["userID"] = userId.ToString();
            parameters["apiKey"] = apiKey;
            parameters["characterID"] = characterId.ToString();
            ret = GetXmlResult(SendXmlRequest("/char/SkillInTraining.xml.aspx", parameters), out result, out cacheUntil);
            if (ret.Code != ErrorCode.Success) return ret;

            training = new SkillTraining(result);

            userCache[CharacterId].SkillTraining = new Cache<SkillTraining>(timeDiff);
            userCache[CharacterId].SkillTraining.Value = training;
            userCache[CharacterId].SkillTraining.CacheUntil = cacheUntil;
            PersistUserCache();
            return ret;
        }
        /// <summary>
        /// Gets a list of up to 1000 wallet journal entries.
        /// </summary>
        /// <param name="account">[in] The account key for which to retrieve entries.</param>
        /// <param name="beforeRefId">[in] A value indicating the earliest reference identifier previously retrieved, or <b>0</b> if no entries have been retrieved.</param>
        /// <param name="journal">[out] A list of wallet journal entries.</param>
        /// <returns>An <see cref="Eve.API.EveAPI.Error">Error</see> class representing the result of the operation.</returns>
        /// <remarks>
        /// <note type="note">This method requires the use of a full-access API key.</note>
        /// <para>
        /// The first call to <b>GetWalletJournal</b> for an account key should be issued with the <paramref name="beforeRefId"/> parameter set to 0.  
        /// This will retrieve up to the 1000 most recent transactions (for up to one week's worth of data).  If a journal contains more than 1000 transactions within the last week, it can be walked by calling
        /// <b>GetWalletJournal</b> again with the smallet <see cref="Eve.API.WalletJournalEntry.RefId">RefId</see> value returned in the list.  This process of walking the 
        /// journal can be repeated with each subsequent request using the smallest reference identifier.
        /// </para>
        /// <para>
        /// <note type="note">
        /// If this method is interrupted after the call to CCP is made, the CCP servers will have assumed that the result was recieved correctly,
        /// and will not return the same journal information until the caching period has expired.
        /// </note>
        /// </para>
        /// </remarks>
        public Error GetWalletJournal(WalletAccountKey account, int beforeRefId, out List<WalletJournalEntry> journal)
        {
            RetrieveUserCache();
            if (userCache[CharacterId].WalletJournal.ContainsKey(account) && userCache[CharacterId].WalletJournal[account].ContainsKey(beforeRefId) && userCache[CharacterId].WalletJournal[account][beforeRefId] != null && userCache[CharacterId].WalletJournal[account][beforeRefId].UseCached)
            {
                journal = new List<WalletJournalEntry>(userCache[CharacterId].WalletJournal[account][beforeRefId].Value);
                return cached;
            }
            XmlElement result;
            Error ret = success;
            journal = new List<WalletJournalEntry>();
            Dictionary<string, string> parameters = new Dictionary<string, string>();
            DateTime cacheUntil;
            string url = "";
            parameters["userID"] = userId.ToString();
            parameters["apiKey"] = apiKey;
            parameters["characterID"] = characterId.ToString();
            if (account == WalletAccountKey.Character)
            {
                parameters["accountKey"] = (-(int)account).ToString();
                url = "/char/";
            } else
            {
                parameters["accountKey"] = ((int)account).ToString();
                url = "/corp/";
            }
            parameters["beforeRefID"] = beforeRefId.ToString();

            ret = GetXmlResult(SendXmlRequest(url + "WalletJournal.xml.aspx", parameters), out result, out cacheUntil);
            if (ret.Code != ErrorCode.Success) return ret;

            foreach (XmlElement rowset in result.ChildNodes)
            {
                if (rowset.Attributes["name"].Value == "entries")
                {
                    foreach (XmlElement row in rowset.ChildNodes)
                        journal.Add(new WalletJournalEntry(row));
                }
            }
            if (!userCache[CharacterId].WalletJournal.ContainsKey(account)) userCache[CharacterId].WalletJournal[account] = new Dictionary<int, Cache<List<WalletJournalEntry>>>();
            userCache[CharacterId].WalletJournal[account][beforeRefId] = new Cache<List<WalletJournalEntry>>(timeDiff);
            userCache[CharacterId].WalletJournal[account][beforeRefId].Value = new List<WalletJournalEntry>(journal);
            userCache[CharacterId].WalletJournal[account][beforeRefId].CacheUntil = cacheUntil;
            PersistUserCache();
            return ret;
        }
        /// <summary>
        /// Gets a list of up to 1000 wallet transactions.
        /// </summary>
        /// <param name="account">[in] The account key for which to retrieve transactions.</param>
        /// <param name="beforeTransId">[in] A value indicating the earliest transaction identifier previously retrieved, or <b>0</b> if no entries have been retrieved.</param>
        /// <param name="trans">[out] A list of wallet transactions.</param>
        /// <returns>An <see cref="Eve.API.EveAPI.Error">Error</see> class representing the result of the operation.</returns>
        /// <remarks>
        /// <note type="note">This method requires the use of a full-access API key.</note>
        /// <para>
        /// The first call to <b>GetWalletTransactions</b> for an account key should be issued with the <paramref name="beforeTransId"/> parameter set to 0.  
        /// This will retrieve up to the 1000 most recent transactions (for up to one week's worth of data).  If a wallet contains more than 1000 transactions within the last week, it can be walked by calling
        /// <b>GetWalletTransactions</b> again with the smallet <see cref="Eve.API.WalletTransaction.TransactionId">TransactionId</see> value returned in the list.  This process of walking the 
        /// journal can be repeated with each subsequent request using the smallest transaction identifier.
        /// </para>
        /// <para>
        /// <note type="note">
        /// If this method is interrupted after the call to CCP is made, the CCP servers will have assumed that the result was recieved correctly,
        /// and will not return the same transaction information until the caching period has expired.
        /// </note>
        /// </para>
        /// </remarks>
        public Error GetWalletTransactions(WalletAccountKey account, int beforeTransId, out List<WalletTransaction> trans)
        {
            RetrieveUserCache();
            if (userCache[CharacterId].WalletTransaction.ContainsKey(account) && userCache[CharacterId].WalletTransaction[account].ContainsKey(beforeTransId) && userCache[CharacterId].WalletTransaction[account][beforeTransId] != null && userCache[CharacterId].WalletTransaction[account][beforeTransId].UseCached)
            {
                trans = new List<WalletTransaction>(userCache[CharacterId].WalletTransaction[account][beforeTransId].Value);
                return cached;
            }
            XmlElement result;
            Error ret = success;
            trans = new List<WalletTransaction>();
            Dictionary<string, string> parameters = new Dictionary<string, string>();
            DateTime cacheUntil;
            string url = "";
            parameters["userID"] = userId.ToString();
            parameters["apiKey"] = apiKey;
            parameters["characterID"] = characterId.ToString();
            if (account == WalletAccountKey.Character)
            {
                parameters["accountKey"] = (-(int)account).ToString();
                url = "/char/";
            }
            else
            {
                parameters["accountKey"] = ((int)account).ToString();
                url = "/corp/";
            }
            parameters["beforeTransID"] = beforeTransId.ToString();

            ret = GetXmlResult(SendXmlRequest(url + "WalletTransactions.xml.aspx", parameters), out result, out cacheUntil);
            if (ret.Code != ErrorCode.Success) return ret;

            foreach (XmlElement rowset in result.ChildNodes)
            {
                if (rowset.Attributes["name"].Value == "transactions")
                {
                    foreach (XmlElement row in rowset.ChildNodes)
                        trans.Add(new WalletTransaction(row));
                }
            }
            if (!userCache[CharacterId].WalletTransaction.ContainsKey(account)) userCache[CharacterId].WalletTransaction[account] = new Dictionary<int, Cache<List<WalletTransaction>>>();
            userCache[CharacterId].WalletTransaction[account][beforeTransId] = new Cache<List<WalletTransaction>>(timeDiff);
            userCache[CharacterId].WalletTransaction[account][beforeTransId].Value = new List<WalletTransaction>(trans);
            userCache[CharacterId].WalletTransaction[account][beforeTransId].CacheUntil = cacheUntil;
            PersistUserCache();
            return ret;
        }
        /// <summary>
        /// Gets balance values for character or corporation wallets.
        /// </summary>
        /// <param name="corp">[in] <b>true</b> to retrieve balances of the character's corporation wallet division; <b>false</b> to retrieve the character's wallet balance.</param>
        /// <param name="balance">[out] A <b>Dictionary</b> containing balances for all wallets the character has permission to view.</param>
        /// <returns>An <see cref="Eve.API.EveAPI.Error">Error</see> class representing the result of the operation.</returns>
        /// <remarks>
        /// <note type="note">This method requires the use of a full-access API key.</note>
        /// </remarks>
        public Error GetWalletBalances(bool corp, out Dictionary<WalletAccountKey, WalletAccount> balance)
        {
            RetrieveUserCache();
            if (corp && userCache[CharacterId].CorporateBalance != null && userCache[CharacterId].CorporateBalance.UseCached)
            {
                balance = userCache[CharacterId].CorporateBalance.Value;
                return cached;
            }
            else if (!corp && userCache[CharacterId].CharacterBalance != null && userCache[CharacterId].CharacterBalance.UseCached)
            {
                balance = userCache[CharacterId].CharacterBalance.Value;
                return cached;
            }
            XmlElement result;
            Error ret = success;
            balance = new Dictionary<WalletAccountKey, WalletAccount>();
            Dictionary<string, string> parameters = new Dictionary<string, string>();
            DateTime cacheUntil;
            string url = corp ? "/corp/" : "/char/";
            parameters["userID"] = userId.ToString();
            parameters["apiKey"] = apiKey;
            parameters["characterID"] = characterId.ToString();
            ret = GetXmlResult(SendXmlRequest(url + "AccountBalance.xml.aspx", parameters), out result, out cacheUntil);
            if (ret.Code != ErrorCode.Success) return ret;

            foreach (XmlElement rowset in result.ChildNodes)
            {
                if (rowset.Attributes["name"].Value == "accounts")
                {
                    foreach (XmlElement row in rowset.ChildNodes)
                    {
                        int id = int.Parse(row.Attributes["accountID"].Value);
                        int key = int.Parse(row.Attributes["accountKey"].Value);
                        double bal = double.Parse(row.Attributes["balance"].Value);
                        if (!corp) key = -key;
                        WalletAccountKey wkey = (WalletAccountKey)key;
                        WalletAccount wa = new WalletAccount();
                        wa.Id = id;
                        wa.Balance = bal;
                        balance[wkey] = wa;
                    }
                }
            }
            if (corp)
            {
                userCache[CharacterId].CorporateBalance = new Cache<Dictionary<WalletAccountKey, WalletAccount>>(timeDiff);
                userCache[CharacterId].CorporateBalance.Value = balance;
                userCache[CharacterId].CorporateBalance.CacheUntil = cacheUntil;
            }
            else
            {
                userCache[CharacterId].CharacterBalance = new Cache<Dictionary<WalletAccountKey, WalletAccount>>(timeDiff);
                userCache[CharacterId].CharacterBalance.Value = balance;
                userCache[CharacterId].CharacterBalance.CacheUntil = cacheUntil;
            }
            PersistUserCache();
            return ret;
        }
        /// <summary>
        /// Gets corporation member tracking information.
        /// </summary>
        /// <param name="track">[out] A list of tracking information on members of the character's corporation.</param>
        /// <returns>An <see cref="Eve.API.EveAPI.Error">Error</see> class representing the result of the operation.</returns>
        /// <remarks>
        /// <note type="note">This method requires the use of a full-access API key.</note>
        /// </remarks>
        public Error GetMemberTrackingData(out List<MemberTracking> track)
        {
            RetrieveUserCache();
            if (userCache[CharacterId].MemberTracking != null && userCache[CharacterId].MemberTracking.UseCached)
            {
                track = new List<MemberTracking>(userCache[CharacterId].MemberTracking.Value);
                return cached;
            }
            XmlElement result;
            Error ret = success;
            track = new List<MemberTracking>();
            Dictionary<string, string> parameters = new Dictionary<string, string>();
            DateTime cacheUntil;
            parameters["userID"] = userId.ToString();
            parameters["apiKey"] = apiKey;
            parameters["characterID"] = characterId.ToString();
            ret = GetXmlResult(SendXmlRequest("/corp/MemberTracking.xml.aspx", parameters), out result, out cacheUntil);
            if (ret.Code != ErrorCode.Success) return ret;
            foreach (XmlElement rowset in result.ChildNodes)
            {
                if (rowset.Attributes["name"].Value == "members")
                {
                    foreach (XmlElement row in rowset.ChildNodes)
                        track.Add(new MemberTracking(row));
                }
            }
            userCache[CharacterId].MemberTracking = new Cache<List<MemberTracking>>(timeDiff);
            userCache[CharacterId].MemberTracking.Value = new List<MemberTracking>(track);
            userCache[CharacterId].MemberTracking.CacheUntil = cacheUntil;
            PersistUserCache();
            return ret;
        }
        /// <summary>
        /// Causes <b>EveAPI</b> to write any account-specific cached data to disk, so that it will persist across sessions.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method should be called when the <see cref="Eve.API.EveAPI.GetCharacters">GetCharacters</see>, <see cref="Eve.API.EveAPI.GetCharacterSheet">GetCharacterSheet</see>
        /// , <see cref="Eve.API.EveAPI.GetSkillInTraining">GetSkillInTraining</see>, <see cref="Eve.API.EveAPI.GetWalletJournal">GetWalletJournal</see>, 
        /// <see cref="Eve.API.EveAPI.GetWalletTransactions">GetWalletTransactions</see>, and <see cref="Eve.API.EveAPI.GetMemberTrackingData">GetMemberTrackingData</see>
        /// methods are used.  This will allow <b>EveAPI</b> to persist cached data across sessions and reduce load on the CCP servers.
        /// </para>
        /// <para>To change the location to which the data is cached set the <see cref="Eve.API.EveAPI.UserCacheFile">UserCacheFile</see> property.</para>
        /// </remarks>
        /// <seealso cref="Eve.API.EveAPI.UserCacheFile"/>
        private void PersistUserCache()
        {
            FileStream fs = new FileStream(UserCacheFile, FileMode.Create, FileAccess.Write, FileShare.None);
            BinaryFormatter bf = new BinaryFormatter();

            bf.Serialize(fs, UserCacheVersion);
            bf.Serialize(fs, userCache);

            fs.Close();
        }
        private void RetrieveUserCache()
        {
            if (userCacheRetrieved) return;
            userCacheRetrieved = true;
            FileStream fs;
            try { fs = new FileStream(UserCacheFile, FileMode.Open, FileAccess.Read, FileShare.Read); }
            catch { return; }
            if (fs == null) return;
            BinaryFormatter bf = new BinaryFormatter();

            int userCacheVersion;
            try { userCacheVersion = (int)bf.Deserialize(fs); }
            catch 
            {
                fs.Close();
                File.Delete(UserCacheFile);
                return;
            }
            if (userCacheVersion != UserCacheVersion)
            {
                fs.Close();
                File.Delete(UserCacheFile);
                return;
            }
            userCache = (UserCache)bf.Deserialize(fs);
            fs.Close();
        }

        private static Error GetXmlResult(XmlDocument xd, out XmlElement result)
        {
            DateTime cacheUntil;
            return GetXmlResult(xd, out result, out cacheUntil);
        }
        private static Error GetXmlResult(XmlDocument xd, out XmlElement result, out DateTime cacheUntil)
        {
            result = null;
            cacheUntil = DateTime.MinValue;
            XmlElement api = xd["eveapi"];
            int version = int.Parse(api.Attributes["version"].Value);
            if (version > SupportedVersion) return unsupported;
            currentEveTime = DateTime.Parse(api["currentTime"].InnerText);
            timeDiff = DateTime.Now - currentEveTime;
            if (api["error"] != null)
            {
                int code = int.Parse(api["error"].Attributes["code"].Value);
                return new Error((ErrorCode)code, api["error"].InnerText);
            }
            result = api["result"];
            XmlElement cu = api["cachedUntil"];
            if (cu != null) cacheUntil = DateTime.Parse(cu.InnerText);
            return success;
        }
        private static XmlDocument SendXmlRequest(string url)
        {
            return SendXmlRequest(url, new Dictionary<string, string>());
        }
        private static XmlDocument SendXmlRequest(string url, Dictionary<string, string> parameters)
        {
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://" + EveAPIURL + "/" + url);
            req.Method = "POST";
            req.ContentType = "application/x-www-form-urlencoded";

            string data = "";
            foreach (KeyValuePair<string, string> kvp in parameters)
            {
                if (data != "") data += "&";
                data += kvp.Key + "=" + kvp.Value;
            }
            byte[] dataBytes = Encoding.ASCII.GetBytes(data);
            req.ContentLength = dataBytes.Length;

            Stream rs = req.GetRequestStream();
            rs.Write(dataBytes, 0, dataBytes.Length);
            rs.Close();
            WebResponse resp = req.GetResponse();
            Stream s = resp.GetResponseStream();
            XmlDocument xd = new XmlDocument();
            xd.Load(s);
            return xd;
        }

        internal static Attribute GetAttribute(string name)
        {
            switch (name)
            {
                case "intelligence": return Attribute.Intelligence;
                case "memory": return Attribute.Memory;
                case "charisma": return Attribute.Charisma;
                case "perception": return Attribute.Perception;
                case "willpower": return Attribute.Willpower;
            }
            return 0;
        }

    }
}
